#include "stdafx.h"
#include "Utils.h"
#include "HeapAllocator.h"
#include <ippi.h>
#include <ipps.h>

using namespace FTLib;

/*static*/const char* Utils::WICPixelFormatToInformalString(const GUID& guid)
{
	static const struct {
		GUID guid;
		const char* sz;
	} guid_and_name[] = {
		{ GUID_WICPixelFormatDontCare, "GUID_WICPixelFormatDontCare" },
		{ GUID_WICPixelFormat1bppIndexed, "GUID_WICPixelFormat1bppIndexed" },
		{ GUID_WICPixelFormat2bppIndexed, "GUID_WICPixelFormat2bppIndexed" },
		{ GUID_WICPixelFormat4bppIndexed, "GUID_WICPixelFormat4bppIndexed" },
		{ GUID_WICPixelFormat8bppIndexed, "GUID_WICPixelFormat8bppIndexed" },
		{ GUID_WICPixelFormatBlackWhite, "GUID_WICPixelFormatBlackWhite" },
		{ GUID_WICPixelFormat2bppGray, "GUID_WICPixelFormat2bppGray" },
		{ GUID_WICPixelFormat4bppGray, "GUID_WICPixelFormat4bppGray" },
		{ GUID_WICPixelFormat8bppGray, "GUID_WICPixelFormat8bppGray" },
		{ GUID_WICPixelFormat8bppAlpha, "GUID_WICPixelFormat8bppAlpha" },
		{ GUID_WICPixelFormat16bppBGR555, "GUID_WICPixelFormat16bppBGR555" },
		{ GUID_WICPixelFormat16bppBGR565, "GUID_WICPixelFormat16bppBGR565" },
		{ GUID_WICPixelFormat16bppBGRA5551, "GUID_WICPixelFormat16bppBGRA5551" },
		{ GUID_WICPixelFormat16bppGray, "GUID_WICPixelFormat16bppGray" },
		{ GUID_WICPixelFormat24bppBGR, "GUID_WICPixelFormat24bppBGR" },
		{ GUID_WICPixelFormat24bppRGB, "GUID_WICPixelFormat24bppRGB" },
		{ GUID_WICPixelFormat32bppBGR, "GUID_WICPixelFormat32bppBGR" },
		{ GUID_WICPixelFormat32bppBGRA, "GUID_WICPixelFormat32bppBGRA" },
		{ GUID_WICPixelFormat32bppPBGRA, "GUID_WICPixelFormat32bppPBGRA" },
		{ GUID_WICPixelFormat32bppGrayFloat, "GUID_WICPixelFormat32bppGrayFloat" },
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
		{ GUID_WICPixelFormat32bppRGB, "GUID_WICPixelFormat32bppRGB" },
#endif
		{ GUID_WICPixelFormat32bppRGBA, "GUID_WICPixelFormat32bppRGBA" },
		{ GUID_WICPixelFormat32bppPRGBA, "GUID_WICPixelFormat32bppPRGBA" },
		{ GUID_WICPixelFormat48bppRGB, "GUID_WICPixelFormat48bppRGB" },
		{ GUID_WICPixelFormat48bppBGR, "GUID_WICPixelFormat48bppBGR" },
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
		{ GUID_WICPixelFormat64bppRGB, "GUID_WICPixelFormat64bppRGB" },
#endif
		{ GUID_WICPixelFormat64bppRGBA, "GUID_WICPixelFormat64bppRGBA" },
		{ GUID_WICPixelFormat64bppBGRA, "GUID_WICPixelFormat64bppBGRA" },
		{ GUID_WICPixelFormat64bppPRGBA, "GUID_WICPixelFormat64bppPRGBA" },
		{ GUID_WICPixelFormat64bppPBGRA, "GUID_WICPixelFormat64bppPBGRA" },
		{ GUID_WICPixelFormat16bppGrayFixedPoint, "GUID_WICPixelFormat16bppGrayFixedPoint" },
		{ GUID_WICPixelFormat32bppBGR101010, "GUID_WICPixelFormat32bppBGR101010" },
		{ GUID_WICPixelFormat48bppRGBFixedPoint, "GUID_WICPixelFormat48bppRGBFixedPoint" },
		{ GUID_WICPixelFormat48bppBGRFixedPoint, "GUID_WICPixelFormat48bppBGRFixedPoint" },
		{ GUID_WICPixelFormat96bppRGBFixedPoint, "GUID_WICPixelFormat96bppRGBFixedPoint" },
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
		{ GUID_WICPixelFormat96bppRGBFloat, "GUID_WICPixelFormat96bppRGBFloat" },
#endif
		{ GUID_WICPixelFormat128bppRGBAFloat, "GUID_WICPixelFormat128bppRGBAFloat" },
		{ GUID_WICPixelFormat128bppPRGBAFloat, "GUID_WICPixelFormat128bppPRGBAFloat" },
		{ GUID_WICPixelFormat128bppRGBFloat, "GUID_WICPixelFormat128bppRGBFloat" },
		{ GUID_WICPixelFormat32bppCMYK, "GUID_WICPixelFormat32bppCMYK" },
		{ GUID_WICPixelFormat64bppRGBAFixedPoint, "GUID_WICPixelFormat64bppRGBAFixedPoint" },
		{ GUID_WICPixelFormat64bppBGRAFixedPoint, "" },
		{ GUID_WICPixelFormat64bppRGBFixedPoint, "GUID_WICPixelFormat64bppRGBFixedPoint" },
		{ GUID_WICPixelFormat128bppRGBAFixedPoint, "GUID_WICPixelFormat128bppRGBAFixedPoint" },
		{ GUID_WICPixelFormat128bppRGBFixedPoint, "GUID_WICPixelFormat128bppRGBFixedPoint" },
		{ GUID_WICPixelFormat64bppRGBAHalf, "GUID_WICPixelFormat64bppRGBAHalf" },
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
		{ GUID_WICPixelFormat64bppPRGBAHalf, "GUID_WICPixelFormat64bppPRGBAHalf" },
#endif
		{ GUID_WICPixelFormat64bppRGBHalf, "GUID_WICPixelFormat64bppRGBHalf" },
		{ GUID_WICPixelFormat48bppRGBHalf, "GUID_WICPixelFormat48bppRGBHalf" },
		{ GUID_WICPixelFormat32bppRGBE, "GUID_WICPixelFormat32bppRGBE" },
		{ GUID_WICPixelFormat16bppGrayHalf, "GUID_WICPixelFormat16bppGrayHalf" },
		{ GUID_WICPixelFormat32bppGrayFixedPoint, "" },
		{ GUID_WICPixelFormat32bppRGBA1010102, "GUID_WICPixelFormat32bppGrayFixedPoint" },
		{ GUID_WICPixelFormat32bppRGBA1010102XR, "GUID_WICPixelFormat32bppRGBA1010102XR" },
		{ GUID_WICPixelFormat64bppCMYK, "GUID_WICPixelFormat64bppCMYK" },
		{ GUID_WICPixelFormat24bpp3Channels, "GUID_WICPixelFormat24bpp3Channels" },
		{ GUID_WICPixelFormat32bpp4Channels, "GUID_WICPixelFormat32bpp4Channels" },
		{ GUID_WICPixelFormat40bpp5Channels, "GUID_WICPixelFormat40bpp5Channels" },
		{ GUID_WICPixelFormat48bpp6Channels, "GUID_WICPixelFormat48bpp6Channels" },
		{ GUID_WICPixelFormat56bpp7Channels, "GUID_WICPixelFormat56bpp7Channels" },
		{ GUID_WICPixelFormat64bpp8Channels, "GUID_WICPixelFormat64bpp8Channels" },
		{ GUID_WICPixelFormat48bpp3Channels, "GUID_WICPixelFormat48bpp3Channels" },
		{ GUID_WICPixelFormat64bpp4Channels, "GUID_WICPixelFormat64bpp4Channels" },
		{ GUID_WICPixelFormat80bpp5Channels, "GUID_WICPixelFormat80bpp5Channels" },
		{ GUID_WICPixelFormat96bpp6Channels, "GUID_WICPixelFormat96bpp6Channels" },
		{ GUID_WICPixelFormat112bpp7Channels, "GUID_WICPixelFormat112bpp7Channels" },
		{ GUID_WICPixelFormat128bpp8Channels, "GUID_WICPixelFormat128bpp8Channels" },
		{ GUID_WICPixelFormat40bppCMYKAlpha, "GUID_WICPixelFormat40bppCMYKAlpha" },
		{ GUID_WICPixelFormat80bppCMYKAlpha, "GUID_WICPixelFormat80bppCMYKAlpha" },
		{ GUID_WICPixelFormat32bpp3ChannelsAlpha, "GUID_WICPixelFormat32bpp3ChannelsAlpha" },
		{ GUID_WICPixelFormat40bpp4ChannelsAlpha, "GUID_WICPixelFormat32bpp3ChannelsAlpha" },
		{ GUID_WICPixelFormat48bpp5ChannelsAlpha, "GUID_WICPixelFormat48bpp5ChannelsAlpha" },
		{ GUID_WICPixelFormat56bpp6ChannelsAlpha, "GUID_WICPixelFormat56bpp6ChannelsAlpha" },
		{ GUID_WICPixelFormat64bpp7ChannelsAlpha, "GUID_WICPixelFormat64bpp7ChannelsAlpha" },
		{ GUID_WICPixelFormat72bpp8ChannelsAlpha, "GUID_WICPixelFormat72bpp8ChannelsAlpha" },
		{ GUID_WICPixelFormat64bpp3ChannelsAlpha, "GUID_WICPixelFormat64bpp3ChannelsAlpha" },
		{ GUID_WICPixelFormat80bpp4ChannelsAlpha, "GUID_WICPixelFormat80bpp4ChannelsAlpha" },
		{ GUID_WICPixelFormat96bpp5ChannelsAlpha, "GUID_WICPixelFormat96bpp5ChannelsAlpha" },
		{ GUID_WICPixelFormat112bpp6ChannelsAlpha, "GUID_WICPixelFormat112bpp6ChannelsAlpha" },
		{ GUID_WICPixelFormat128bpp7ChannelsAlpha, "GUID_WICPixelFormat128bpp7ChannelsAlpha" },
		{ GUID_WICPixelFormat144bpp8ChannelsAlpha, "GUID_WICPixelFormat144bpp8ChannelsAlpha" },
		{ GUID_WICPixelFormat8bppY, "GUID_WICPixelFormat8bppY" },
		{ GUID_WICPixelFormat8bppCb, "GUID_WICPixelFormat8bppCb" },
		{ GUID_WICPixelFormat8bppCr, "GUID_WICPixelFormat8bppCr" },
		{ GUID_WICPixelFormat16bppCbCr, "GUID_WICPixelFormat16bppCbCr" }
	};

	for (int i = 0; i < ARRAYSIZE(guid_and_name); ++i)
	{
		if (guid == guid_and_name[i].guid)
		{
			return guid_and_name[i].sz;
		}
	}

	return "unknown";
}

/*static*/void Utils::ComplexToTwoReals(std::shared_ptr<IBitmapData> input, std::shared_ptr<IBitmapData>& output1, std::shared_ptr<IBitmapData>& output2)
{
	output1 = CBitmapData<CHeapAllocator>::Create(PixelType::GrayFloat32, input->GetWidth(), input->GetHeight());
	output2 = CBitmapData<CHeapAllocator>::Create(PixelType::GrayFloat32, input->GetWidth(), input->GetHeight());

	auto srcBd = input->Lock();
	auto out1Bd = output1->Lock();
	auto out2Bd = output2->Lock();

	for (int y = 0; y < input->GetHeight(); ++y)
	{
		const float* ptrSrc = (const float*)(((char*)srcBd.ptrDataRoi) + y * srcBd.pitch);
		float* ptrOut1 = (float*)(((char*)out1Bd.ptrDataRoi) + y * out1Bd.pitch);
		float* ptrOut2 = (float*)(((char*)out2Bd.ptrDataRoi) + y * out2Bd.pitch);
		for (int x = 0; x < input->GetWidth(); ++x)
		{
			*(ptrOut1 + x) = *(ptrSrc + x * 2);
			*(ptrOut2 + x) = *(ptrSrc + x * 2 + 1);
		}
	}

	input->Unlock();
	output1->Unlock();
	output2->Unlock();
}

/*static*/std::shared_ptr<IBitmapData> Utils::RealToGray16NormalizedDestroyInput(std::shared_ptr<IBitmapData> input)
{
	if (input->GetPixelType() != PixelType::GrayFloat32)
	{
		throw std::invalid_argument("Input must be of type GrayFloat32.");
	}

	float minVal, maxVal;
	auto data = input->Lock();
	IppiSize size = { input->GetWidth(), input->GetHeight() };
	/*float f1, f2;
	f1 = *((const float*)(data.ptrDataRoi));
	f2 = *(((const float*)(data.ptrDataRoi)) + 1);
	memset(data.ptrDataRoi, 0, 2 * sizeof(float));*/

	Utils::CalcMinMaxValues_32f(input, 0.5, 99.5, &minVal, &maxVal);

	//ippiMinMax_32f_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, size, &minVal, &maxVal);
	ippiSubC_32f_C1IR(minVal, (Ipp32f*)data.ptrDataRoi, data.pitch, size);
	ippiMulC_32f_C1IR(65535.0f / (maxVal - minVal), (Ipp32f*)data.ptrDataRoi, data.pitch, size);
	auto output = CBitmapData<CHeapAllocator>::Create(PixelType::Gray16, input->GetWidth(), input->GetHeight());
	auto destData = output->Lock();
	ippiConvert_32f16u_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, (Ipp16u*)destData.ptrDataRoi, destData.pitch, size, ippRndNear);
	output->Unlock();
	input->Unlock();
	return output;
}

/*static*/std::shared_ptr<IBitmapData> Utils::RealToGray8NormalizedDestroyInput(std::shared_ptr<IBitmapData> input)
{
	if (input->GetPixelType() != PixelType::GrayFloat32)
	{
		throw std::invalid_argument("Input must be of type GrayFloat32.");
	}

	float minVal, maxVal;
	auto data = input->Lock();
	IppiSize size = { input->GetWidth(), input->GetHeight() };
	ippiMinMax_32f_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, size, &minVal, &maxVal);
	ippiSubC_32f_C1IR(minVal, (Ipp32f*)data.ptrDataRoi, data.pitch, size);
	ippiMulC_32f_C1IR(255.0f / (maxVal - minVal), (Ipp32f*)data.ptrDataRoi, data.pitch, size);
	auto output = CBitmapData<CHeapAllocator>::Create(PixelType::Gray8, input->GetWidth(), input->GetHeight());
	auto destData = output->Lock();
	ippiConvert_32f8u_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, (Ipp8u*)destData.ptrDataRoi, destData.pitch, size, ippRndNear);
	output->Unlock();
	input->Unlock();
	return output;
}

/*static*/int Utils::CalcOrder(uint32_t x)
{
	switch (x)
	{
	case 0x1:
		return 0;
	case 0x2:
		return 1;
	case 0x4:
		return 2;
	case 0x8:
		return 3;
	case 0x10:
		return 4;
	case 0x20:
		return 5;
	case 0x40:
		return 6;
	case 0x80:
		return 7;
	case 0x100:
		return 8;
	case 0x200:
		return 9;
	case 0x400:
		return 10;
	case 0x800:
		return 11;
	case 0x1000:
		return 12;
	case 0x2000:
		return 13;
	case 0x4000:
		return 14;
	case 0x8000:
		return 15;
	case 0x10000:
		return 16;
	case 0x20000:
		return 17;
	case 0x40000:
		return 18;
	case 0x80000:
		return 19;
	case 0x100000:
		return 20;
	case 0x200000:
		return 21;
	case 0x400000:
		return 22;
	case 0x800000:
		return 23;
	case 0x1000000:
		return 24;
	}

	return -1;
}

/*static*/void Utils::MultiplyWithOneAndMinusOne(float* ptr, uint32_t pitch, uint32_t width, uint32_t height)
{
	for (uint32_t i = 0; i < width; ++i)
	{
		for (uint32_t j = 0; j < height; ++j)
		{
			uint32_t n = i + j;
			if (n & 1)
			{
				*((float*)(((char*)ptr) + j*pitch + i*sizeof(float))) *= -1;
			}
		}
	}
}


/*static*/void Utils::CalcMinMaxValues_32f(std::shared_ptr<IBitmapData> bm, double minPercentile, double maxPercentile, float* ptrMin, float* ptrMax)
{
	float minVal, maxVal;
	auto data = bm->Lock();
	IppiSize size = { bm->GetWidth(), bm->GetHeight() };
	ippiMinMax_32f_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, size, &minVal, &maxVal);
	if (minPercentile <= 0 && maxPercentile >= 100)
	{
		bm->Unlock();
		if (ptrMin != nullptr)
		{
			*ptrMin = minVal;
		}

		if (ptrMax != nullptr)
		{
			*ptrMax = maxVal;
		}

		return;
	}

	// adjust min and max a little bit in order to make sure that they are counted
	float diff = maxVal - minVal;
	float adjust = diff * 0.0001f;
	minVal -= adjust;
	maxVal += adjust;

	const int nBins = 256 * 4;
	int nLevels[] = { nBins + 1 };
	int sizeHistObj, sizeBuffer;

	// get sizes for spec and buffer
	ippiHistogramGetBufferSize(ipp8u, size, nLevels, 1/*nChan*/, 1/*uniform*/, &sizeHistObj, &sizeBuffer);

	Ipp8u* pBuffer;
	Ipp32u pHistVec[nBins];
	IppiHistogramSpec* pHistObj;
	pHistObj = (IppiHistogramSpec*)ippsMalloc_8u(sizeHistObj);
	pBuffer = (Ipp8u*)ippsMalloc_8u(sizeBuffer);

	Ipp32f lowerLevel[] = { minVal };
	Ipp32f upperLevel[] = { maxVal };

	// initialize spec
	ippiHistogramUniformInit(ipp32f, lowerLevel, upperLevel, nLevels, 1, pHistObj);

	Ipp32f pLevels[nBins + 1];
	Ipp32f* ppLevels[1] = { pLevels };
	ippiHistogramGetLevels(pHistObj, ppLevels);

	ippiHistogram_32f_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, size, pHistVec, pHistObj, pBuffer);

	bm->Unlock();

	uint32_t minPercentileCount = (minPercentile / 100)*size.width*size.height;
	uint32_t maxPercentileCount = (maxPercentile / 100)*size.width*size.height;

	uint32_t count = 0;
	bool minFound, maxFound;
	minFound = maxFound = false;
	for (uint32_t i = 0; i < nBins + 1 && (minFound & maxFound) == false; ++i)
	{
		count += pHistVec[i];
		if (minFound == false && count >= minPercentileCount)
		{
			if (ptrMin != nullptr)
			{
				*ptrMin = pLevels[i];
			}

			minFound = true;
		}

		if (maxFound == false && count >= maxPercentileCount)
		{
			if (ptrMax != nullptr)
			{
				*ptrMax = pLevels[i];
			}

			maxFound = true;
		}
	}


	ippsFree(pHistObj);
	ippsFree(pBuffer);
}

/*static*/void Utils::NormalizeFloatImage(std::shared_ptr<FTLib::IBitmapData> image)
{
	// TODO: check whether we really have a float image
	float minVal, maxVal;
	auto data = image->Lock();
	IppiSize size = { image->GetWidth(), image->GetHeight() };
	ippiMinMax_32f_C1R((const Ipp32f*)data.ptrDataRoi, data.pitch, size, &minVal, &maxVal);
	
	if (minVal == maxVal)
	{
		// well, in that case... we better to nothing (we could set the image to 0.5f or so?)
		return;
	}

	if (minVal != 0)
	{
		ippiSubC_32f_C1IR(minVal, (float*)data.ptrDataRoi, data.pitch, size);
	}

	// and now -> scale
	float factor = maxVal - minVal;
	ippiDivC_32f_C1IR(factor, (float*)data.ptrDataRoi, data.pitch, size);
	image->Unlock();
}

/*static*/void Utils::NormalizeFloatImage(std::shared_ptr<FTLib::IBitmapData> image, float minValue, float maxValue)
{
	IppiSize size = { image->GetWidth(), image->GetHeight() };
	auto data = image->Lock();
	if (minValue == maxValue)
	{
		// well, in that case... we better to nothing (we could set the image to 0.5f or so?)
		return;
	}

	if (minValue != 0)
	{
		ippiSubC_32f_C1IR(minValue, (float*)data.ptrDataRoi, data.pitch, size);
	}

	// and now -> scale
	float factor = maxValue - minValue;
	ippiDivC_32f_C1IR(factor, (float*)data.ptrDataRoi, data.pitch, size);
	image->Unlock();
}

/*static*/std::uint32_t Utils::GetBytesPerPel(FTLib::PixelType pixeltype)
{
	switch (pixeltype)
	{
	case FTLib::PixelType::GrayFloat32:
		return 4;
	case FTLib::PixelType::ComplexFloat32:
		return 2 * 4;
	case FTLib::PixelType::Gray16:
		return 2;
	case FTLib::PixelType::Gray8:
		return 1;
	case FTLib::PixelType::ComplexDouble64:
		return 2 * 8;
	case FTLib::PixelType::GrayDouble64:
		return 8;
	default:
		throw std::invalid_argument("Unknown pixeltype.");
	}
}